#pragma once

#include <iostream>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

// 这里写条件变量
// 新需求: 我只想保存最新的5个任务，如果来了任务，老的任务，我想让他直接被丢弃

// 默认容量大小
const uint32_t gDefaultCap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(uint32_t cap = gDefaultCap) 
        : cap_(cap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&conCond_, nullptr);
        pthread_cond_init(&proCond_, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&conCond_);
        pthread_cond_destroy(&proCond_);
    }

public:
    // 生产接口
    void push(const T &in) // const &: 纯输入
    {
        // 加锁
        // (生产之前要判断)判断->是否适合生产->bq是否为满->程序员视角的条件->1. 满(不生产) 2. 不满(生产)
        // if(满) 不生产(不仅仅要)，休眠(更要)，休眠期间消费线程就可以去申请锁了
        // else if(不满)生产，唤醒消费者
        // 解锁

        // 先把队列锁住
        lockQueue();
        while (isFull()) // ifFull就是我们在临界区中设定的条件
        {
            // before: 当我等待的时候，会自动释放mutex_(因为不能拿着锁去等)
            proBlockWait(); // 阻塞等待，等待被唤醒。 被唤醒 ！= 条件被满足 (概率虽然很小)，要做到: 被唤醒 && 条件被满足
            // 因为一些原因导致了被伪唤醒了(有可能是系统的问题，也有可能是代码本身有问题，但是要保证代码的健壮性)，继续向后执行

            // after: 当我醒来的时候，我是在临界区里醒来的！！
            // 当线程被伪唤醒后，它会重新参与调度并尝试获取锁。
            // 如果其他线程已经持有了锁，并且没有释放，那么伪唤醒的线程将无法获得锁，它需要继续等待或者重新检查条件是否满足。
            // 从哪里被阻塞，就要从哪里醒来，醒来之后就是相当于没有锁就访问临界资源了
        }

        // 条件满足，可以生产
        pushCore(in); // 生产完成
        // wakeupCon(); // 唤醒消费者
        // 把队列解锁
        unlockQueue();
        wakeupCon(); // 生产完了，生产者就要唤醒消费者
    }

    // 消费接口
    T pop()
    {
        // 加锁
        // 判断->是否适合消费->bq是否为空->程序员视角的条件->1. 空(不消费) 2. 有(消费)
        // if(空) 不消费，休眠
        // else if(有) 消费，唤醒生产者
        // 解锁
        
        // 先把队列锁住
        lockQueue();
        // 如果不加锁，没有资格访问队列
        while (isEmpty())
        {
            conBlockwait(); // 阻塞等待，等待被唤醒？
        }

        // 条件满足，可以消费
        T tmp = popCore();

        // 把队列解锁
        unlockQueue();
        wakeupPro(); // 消费完了，消费者就要唤醒生产者

        return tmp;
    }

private:
    void lockQueue()
    {
        pthread_mutex_lock(&mutex_);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&mutex_);
    }
    bool isEmpty()
    {
        return bq_.empty();
    }
    bool isFull()
    {
        return bq_.size() == cap_;
    }

    // 生产者进行阻塞等待
    void proBlockWait() // 生产者一定是在临界区中的！
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
        pthread_cond_wait(&proCond_, &mutex_);
    }

    // 消费者进行阻塞等待
    void conBlockwait() // 阻塞等待，等待被唤醒
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
        pthread_cond_wait(&conCond_, &mutex_);
        // 2. 当阻塞结束，返回的时候，pthread_cond_wait，会自动帮你重新获得mutex_，然后才返回
    }

    // 唤醒生产者
    void wakeupPro()
    {
        // 一定要在生产者所在的条件变量下唤醒
        pthread_cond_signal(&proCond_);
    }

    // 唤醒消费者
    void wakeupCon()
    {
        // 一定要在消费者所在的条件变量下唤醒
        pthread_cond_signal(&conCond_);
    }

    // 生产完成
    void pushCore(const T &in)
    {
        bq_.push(in);
    }

    // 消费
    T popCore()
    {
        T tmp = bq_.front();
        bq_.pop();

        return tmp;
    }

private:
    uint32_t cap_;           // 容量
    queue<T> bq_;            // blockqueue
    pthread_mutex_t mutex_;  // 保护阻塞队列的互斥锁
    pthread_cond_t conCond_; // 让消费者等待的条件变量
    pthread_cond_t proCond_; // 让生产者等待的条件变量
};
